package com.trainfinder.requestor;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.trainfinder.proxy.Proxy;
import com.trainfinder.proxy.ServerCreationException;
import com.trainfinder.proxy.ThreadCreatedListener;

public class Requestor implements ThreadCreatedListener {

	private final Extractor moveExtractor = new ExtractMoved();

	private Map<String, String> tokens = new HashMap<String, String>();

	private static final Extractor DOC_TYPE_EXTRACTOR = new ExtractDocType();

	public Requestor() {
		String data = request("get-session.txt", tokens);
		tokens.put("___SESSION_ID___", new ExtractSessionID().extract(data));
		redirectRequest(moveExtractor, tokens, data);
	}

	public String getLastPossibleDate(SimpleHtmlParser htmlParser)
			throws UnparsableException {
		String fromStation = "london";
		String toStation = "paris";

		Calendar outwardCalendar = Calendar.getInstance();
		outwardCalendar.add(Calendar.DAY_OF_YEAR, 150);

		Calendar returnCalendar = Calendar.getInstance();
		returnCalendar.add(Calendar.DAY_OF_YEAR, 160);

		addDateTokens(tokens, outwardCalendar, returnCalendar);

		addStationTokens(tokens, fromStation, toStation);

		String data = fullRequest("post-data-request.txt", tokens);

		data = redirectRequest(new ExtractGO(), tokens, data);

		data = new ExtractContent().extract(data);
		return htmlParser.parseOutwardHTML(data);

	}

	public List<IJourney> getJourneis(ExtractValues extractValues,
			HtmlParser htmlParser, Calendar outwardCalendar,
			Calendar returnCalendar, String fromStation, String toStation)
			throws UnparsableException {

		List<IJourney> result = new ArrayList<IJourney>();

		addDateTokens(tokens, outwardCalendar, returnCalendar);

		addStationTokens(tokens, fromStation, toStation);

		String data = fullRequest("post-data-request.txt", tokens);

		data = redirectRequest(new ExtractGO(), tokens, data);

		data = new ExtractContent().extract(data);
		data = DOC_TYPE_EXTRACTOR.extract(data);
		final boolean awayOnSaturdayNight = awayOnSaturdayNight(
				outwardCalendar, returnCalendar);

		// System.out.println(data);
		// FileOutputStream outputStream;
		// try {
		// outputStream = new FileOutputStream("C:\\temp\\data-out.html");
		// outputStream.write(data.getBytes());
		// outputStream.close();
		// } catch (FileNotFoundException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// } catch (IOException e) {
		// // TODO Auto-generated catch block
		// e.printStackTrace();
		// }

		data = htmlParser.tidy(data);

		result.addAll(htmlParser.parseOutwardHTML(data, fromStation, toStation,
				'O', awayOnSaturdayNight));

		String[] values = extractValues.getValues(new ByteArrayInputStream(data
				.getBytes()));

		List<IJourney> returnJournies = new ArrayList<IJourney>();

		for (int i = 0; i < values.length; i++) {

			if (values[i] != null) {
				tokens.put("___JOURNEY_SELECTED___", values[i]);
				returnJournies.addAll(getReturnJournies(htmlParser,
						fromStation, toStation, tokens, awayOnSaturdayNight));
			}
		}

		result.addAll(getUniqueSet(returnJournies));

		return result;
	}

	private boolean awayOnSaturdayNight(Calendar outwardCalendar,
			Calendar returnCalendar) {
		if (returnCalendar.getTime().getTime() <= outwardCalendar.getTime()
				.getTime()) {
			throw new InvalidParameterException(
					"expect the return date to be after the outward date");
		}

		boolean result = false;
		if (outwardCalendar.get(Calendar.DAY_OF_YEAR) != returnCalendar
				.get(Calendar.DAY_OF_YEAR)) {

			Calendar day = Calendar.getInstance();
			day.setTime(outwardCalendar.getTime());

			int dayOfWeek = day.get(Calendar.DAY_OF_WEEK);

			while (day.getTime().getTime() < returnCalendar.getTime().getTime()) {

				// check if away over saturday night
				if (dayOfWeek == Calendar.SATURDAY) {
					result = true;
					break;
				}

				day.add(Calendar.DAY_OF_YEAR, 1);
				dayOfWeek = day.get(Calendar.DAY_OF_WEEK);
			}
		}

		return result;
	}

	private void addStationTokens(Map<String, String> tokens,
			String fromStation, String toStation) {

		{
			String fromStationCode = getStationCode(fromStation);

			tokens.put("___FROM_STATION___", fromStationCode);
		}

		{
			tokens.put("___TO_STATION___", getStationCode(toStation));
		}
	}

	public static String getStationCode(String station) {
		final String result;
		if (station.equals("london")) {
			result = "GBLON";
		} else if (station.equals("paris")) {
			result = "FRPAR";
		} else if (station.equals("brussels")) {
			result = "BEBRU";
		} else if (station.equals("lille")) {
			result = "FRLIL";
		} else {
			throw new InvalidParameterException("unknown station code");
		}
		return result;
	}

	private void addDateTokens(Map<String, String> tokens, Calendar calendar,
			Calendar returnDate) {
		// outward
		{
			String day = Integer.toString(calendar.get(Calendar.DAY_OF_MONTH));
			String month = Integer.toString(calendar.get(Calendar.MONTH) + 1);
			String month_min1 = Integer.toString(calendar.get(Calendar.MONTH));
			String hour = Integer.toString(calendar.get(Calendar.HOUR_OF_DAY));
			String year = Integer.toString(calendar.get(Calendar.YEAR));

			if (hour.length() == 1) {
				hour = "0" + hour;
			}

			if (month.length() == 1) {
				month = "0" + month;
			}

			tokens.put("___OUTWARD_DAY___", day);
			tokens.put("___OUTWARD_MONTH___", month);
			tokens.put("___OUTWARD_MONTH_2___", month_min1);
			tokens.put("___OUTWARD_YEAR___", year);
			tokens.put("___OUTWARD_HOUR___", hour);

		}

		// return
		{

			String day = Integer
					.toString(returnDate.get(Calendar.DAY_OF_MONTH));
			String month = Integer.toString(returnDate.get(Calendar.MONTH));

			String hour = Integer
					.toString(returnDate.get(Calendar.HOUR_OF_DAY));

			if (hour.length() == 1) {
				hour = "0" + hour;
			}

			if (month.length() == 1) {
				month = "0" + month;
			}

			tokens.put("___RETURN_DAY___", day);
			tokens.put("___RETURN_MONTH___", month);
			tokens.put("___RETURN_HOUR___", hour);

		}
	}

	private List<IJourney> getUniqueSet(Collection<IJourney> returnJournies) {
		List<IJourney> result = new ArrayList<IJourney>();

		final Map<String, IJourney> map = new HashMap<String, IJourney>();

		for (Iterator iter = returnJournies.iterator(); iter.hasNext();) {
			IJourney journey = (IJourney) iter.next();
			map.put(journey.getId().toString(), journey);
		}

		for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
			Map.Entry entry = (Map.Entry) iter.next();
			result.add((IJourney) entry.getValue());
		}

		return result;
	}

	private List<IJourney> getReturnJournies(HtmlParser htmlParser,
			String fromStation, String toStation, Map<String, String> tokens,
			boolean awayOnSaturdayNight) {

		String data = fullRequest("get-return-journey.txt", tokens);
		data = new ExtractContent().extract(data);
		data = htmlParser.tidy(data);
		try {
			return htmlParser.parseOutwardHTML(data, fromStation, toStation,
					'R', awayOnSaturdayNight);
		} catch (UnparsableException e) {

			return new ArrayList<IJourney>();
		}
	}

	private String fullRequest(String resourceName, Map<String, String> tokens) {
		String data = request(resourceName, tokens);
		return redirectRequest(moveExtractor, tokens, data);
	}

	private String redirectRequest(Extractor extractor,
			Map<String, String> tokens, String data) {

		String moved = extractor.extract(data);
		while (!"".equals(moved)) {
			tokens.put("___INITIAL_REQUEST_URL___", moved);
			data = request("redirect.txt", tokens);
			moved = moveExtractor.extract(data);
		}
		return data;
	}

	public String request(String resourceName, Map tokens) {
		String result = "";
		final ByteArrayOutputStream outputSteam = new ByteArrayOutputStream();
		Proxy proxy = null;
		try {

			proxy = new Proxy(new SimpleServiceFactory(resourceName, tokens,
					outputSteam));
			proxy.addThreadCreatedListener(this);

			proxy.start();

			remoteThread.join(30000);

			result = outputSteam.toString();
		} catch (ServerCreationException e) {
			// e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			// e.printStackTrace();
		} finally {
			try {
				outputSteam.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (proxy != null) {
				proxy.close();
			}
		}
		return result;

	}

	private Thread remoteThread;

	public void onThreadCreated(Thread t) {
		remoteThread = t;
	}

}
